<template>
  <canvas ref="canvasRef"></canvas>
  <p class="title-text" ref="titleBox" :style="titlePosition">
    {{ tooltopContent.title }}
  </p>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, reactive } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'
import { withBase } from './utils'
import gsap from 'gsap'
import livingImg from '/imgs/livingRoom.jpg'
import kitchenImg from '/imgs/kitchen.jpg'
import tipImg from '/imgs/tip.png'

const canvasRef = ref(null)
const titleBox = ref(null)
let scene, camera, renderer, controls, animationId, sphere
const titlePosition = reactive({
  top: "-100%",
  left: "-100%"
})
let tooltopContent = {} // 展示的内容

let dataList = [
  {
    image: livingImg,
    content: {
      position: { x: -21, y: 0, z: -15 },
      title: "进入厨房", // 标题
      text: "", // 文本内容
      image: 1, // 场景贴图的下标，对应dataList下标
      showTip: false, // 是否展示弹出框
      showTitle: true, // 是否展示提示标题
    }
  },
  {
    image: kitchenImg,
    content: {
      position: { x: -20, y: -4, z: 16 },
      title: "进入客厅", // 标题
      text: "", // 文本内容
      image: 0,
      showTip: false, // 是否展示弹出框
      showTitle: true, // 是否展示提示标题
    }
  }
]
const spriteList = [] // 精灵列表

function main() {
  scene = new THREE.Scene()
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
  renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value, antialias: true })
  renderer.setSize(window.innerWidth, window.innerHeight)
  camera.position.z = 0.1
  {
    // 添加轨道控制器
    controls = new OrbitControls(camera, renderer.domElement)
    controls.enableDamping = true // 开启阻尼
  }
  // 正方体六面VR
  // const geometry = new THREE.BoxGeometry(10, 10, 10)
  // const arr = ['nx', 'px', 'py', 'ny', 'nz' , 'pz']
  // const boxMaterials = arr.map((item, index) => {
  //   const texture = new THREE.TextureLoader().load(withBase(`/imgs/street/${item}.jpg`))
  //   if (item === 'py' || item === 'ny') {
  //     texture.rotation = Math.PI
  //     texture.center.set(0.5, 0.5)
  //     // texture.center = new THREE.Vector2(0.5, 0.5)
  //   }
  //   return new THREE.MeshBasicMaterial({ map: texture })
  // })
  // const cube = new THREE.Mesh(geometry, boxMaterials)
  // cube.geometry.scale(1, 1, -1) // 反转Z轴
  // scene.add(cube)
  // console.log(scene);

  // 球体VR
  const geometry = new THREE.SphereGeometry(10, 32, 32)
  // const loader = new RGBELoader()
  // loader.load(
  //   withBase('/imgs/004.hdr'),
  //   (texture) => {
  //     const material = new THREE.MeshBasicMaterial({ map: texture })
  //     const sphere = new THREE.Mesh(geometry, material)
  //     sphere.geometry.scale(1, 1, -1) // 反转Z轴
  //     scene.add(sphere)
  //   },
  // )
  const texture = new THREE.TextureLoader().load(livingImg)
  const material = new THREE.MeshBasicMaterial({ map: texture })
  sphere = new THREE.Mesh(geometry, material)
  sphere.geometry.scale(10, 10, -10) // 反转Z轴
  scene.add(sphere)

  addTipsSprite()
}
// 添加精灵提示
function addTipsSprite(index = 0) {
  new THREE.TextureLoader().load(tipImg, (texture) => {
    texture.needsUpdate = true
    texture.colorSpace = THREE.SRGBColorSpace

    const spriteMaterial = new THREE.SpriteMaterial({
      map: texture,
      transparent: true,
      alphaTest: 0.8,
    })
    const sprite = new THREE.Sprite(spriteMaterial)
    sprite.scale.set(1, 1, 1)
    // sprite.position.set(-21, 0, -15)
    let content = dataList[index].content
    sprite.position.set(content.position.x, content.position.y, content.position.z)
    sprite.content = content
    spriteList.push(sprite)
    scene.add(sprite)
  })
}
function mousemoveEvent(event) {
  const { clientWidth, clientHeight } = canvasRef.value
  const mouseX = (event.clientX / clientWidth) * 2 - 1
  const mouseY = -(event.clientY / clientHeight) * 2 + 1
  const raycaster = new THREE.Raycaster()
  raycaster.setFromCamera(new THREE.Vector2(mouseX, mouseY), camera)
  const intersects = raycaster.intersectObjects(spriteList, true)
  if (intersects.length > 0) {
    const worldPosition = new THREE.Vector3()
    intersects[0].object.getWorldPosition(worldPosition) // 获取世界坐标
    let position = worldPosition.project(camera) // 将世界坐标转换为屏幕坐标
    tooltopContent = intersects[0].object.content
    if (tooltopContent.showTitle) {
      // 方案一：根据物体坐标计算坐标
      /**
       * position.x * elementWidth：将 NDC 的 [-1, 1] 映射到 [-elementWidth, elementWidth]。
       * + elementWidth：将范围平移为 [0, 2*elementWidth]，即 Canvas 的完整宽度。
       * - titleBox.value.clientWidth / 2：让 HTML 元素的中心点对齐到物体的屏幕位置（否则元素左上角会对齐）。
       */
      let elementWidth = clientWidth / 2
      let elementHeight = clientHeight / 2
      let left = Math.round((position.x * elementWidth) + elementWidth - titleBox.value.clientWidth / 2)
      let top = Math.round((-position.y * elementHeight) + elementHeight - titleBox.value.clientHeight / 2)
      titlePosition.top = `${top}px`
      titlePosition.left = `${left}px`

      // 方案二：根据鼠标计算坐标
      // titlePosition.top = `${Math.round(event.clientY - 30)}px`
      // titlePosition.left = `${Math.round(event.clientX)}px`
    }
  } else {
    mouseleaveEvent()
  }
}
function mouseleaveEvent() {
  titlePosition.top = "-100%"
  titlePosition.left = "-100%"
  tooltopContent = {}
}
function clickEvent() {
  const mouseX = (event.clientX / canvasRef.value.clientWidth) * 2 - 1
  const mouseY = -(event.clientY / canvasRef.value.clientHeight) * 2 + 1
  const raycaster = new THREE.Raycaster()
  raycaster.setFromCamera(new THREE.Vector2(mouseX, mouseY), camera)
  // 射线检测
  const intersects = raycaster.intersectObjects(spriteList, true)
  // 选中第一个
  if (intersects.length > 0) {
    const content = intersects[0].object.content
    if (content.showTitle) {
      changeContent(content.image)
    }
  }
}
function changeContent(index) {
  // 清除场景中精灵标签
  scene.remove(...spriteList)
  spriteList.forEach(sprite => sprite.material.dispose())
  spriteList.length = 0

  // 重新加载贴图
  const texture = new THREE.TextureLoader().load(dataList[index].image)
  const material = new THREE.MeshBasicMaterial({
    map: texture,
    opacity: 0,
    transparent: true
  })
  sphere.material = material
  gsap.to(material, {
    opacity: 1,
    duration: 2,
  })

  // 添加新的精灵标签
  addTipsSprite(index)

  mouseleaveEvent()
}
function animate() {
  if (resizeRendererToDisplaySize(renderer)) {
    const canvas = renderer.domElement
    camera.aspect = canvas.clientWidth / canvas.clientHeight
    camera.updateProjectionMatrix()
  }
  renderer.render(scene, camera)
  animationId = requestAnimationFrame(animate)
}
function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement
  const width = canvas.clientWidth
  const height = canvas.clientHeight
  const needResize = canvas.width !== width || canvas.height !== height
  if (needResize) {
    renderer.setSize(width, height, false)
  }
  return needResize
}
function cleanUp() {
  // 1. 停止动画循环
  if (animationId) cancelAnimationFrame(animationId)

  // 2. 释放Three.js资源
  scene.traverse(child => {
    if (child.isMesh) {
      child.geometry.dispose()
      child.material.dispose()
      // texture.dispose()   // 释放纹理（如果有）
    }
  })

  // 3. 销毁渲染器
  renderer.dispose()
  renderer.forceContextLoss()
  renderer.domElement = null
  renderer = null

  // 4. 销毁控制器
  controls.dispose()
  controls = null

  // 5. 清理引用
  scene = null
  camera = null
  canvasRef.value = null
}

onMounted(() => {
  main()
  animate()
  window.addEventListener('mousemove', mousemoveEvent)
  window.addEventListener('mouseleave', mouseleaveEvent)
  window.addEventListener('click', clickEvent)
})

onBeforeUnmount(() => {
  cleanUp()
  window.removeEventListener('mousemove', mousemoveEvent)
  window.removeEventListener('mouseleave', mouseleaveEvent)
  window.removeEventListener('click', clickEvent)
})
</script>

<style scoped>
.title-text {
  position: fixed;
  color: #fff;
  font-size: 20px;
  font-weight: bold;
  text-align: center;
  background-color: rgba(0, 0, 0, 0.5);
  padding: 10px;
  border-radius: 5px;
  cursor: pointer;
}
</style>
